The goal of this notebook is to compare pseudobulk and bulk calculations to determine which pseudobulk calculation should we proceed with for modeling: the log2 of the sum of raw counts (pseudobulk_log_counts) or the DESeq2-normalized sum of raw counts (pseudobulk_deseq). To this end, we’ll explore pseudobulk expression distributions, compare them to bulk, and also explore distributions of expression where there is disagreement between bulk and single-cell. We also compare to bulk counts to see which quantity more closely approximates those values.

Setup

renv::load()

library(ggplot2)
theme_set(theme_bw())

Paths

data_dir <- here::here("analysis", "pseudobulk-bulk-prediction", "data")
tpm_dir <- file.path(data_dir, "tpm")
pseudobulk_dir <- file.path(data_dir, "pseudobulk")
bulk_counts_file <- file.path(data_dir, "scpca_data", "normalized_bulk_counts.rds")

tpm_files <- list.files(
  path = tpm_dir,
  full.names = TRUE,
  pattern = "-tpm\\.tsv$"
)
tpm_names <- stringr::str_split_i(basename(tpm_files), pattern = "-", i = 1)
names(tpm_files) <- tpm_names


pseudobulk_files <- list.files(
  path = pseudobulk_dir,
  full.names = TRUE,
  pattern = "-pseudobulk\\.tsv$"
)
pseudobulk_names <- stringr::str_split_i(basename(pseudobulk_files), pattern = "-", i = 1)
names(pseudobulk_files) <- pseudobulk_names

# Make sure we have the same projects, in the same order
stopifnot(
  all.equal(names(tpm_files), names(pseudobulk_files))
)

Read and prepare input data

We’ll make both a long and wide version of the data for convenience throughout the notebook.

project_long_df_list <- purrr::map2(
  tpm_files, 
  pseudobulk_files, 
  \(tpm_file, pseudo_file) {
    
    dplyr::bind_rows(
      # TPM needs to be in log2 space
      readr::read_tsv(tpm_file, show_col_types = FALSE) |>
        dplyr::mutate(expression = log2(expression)), 
      readr::read_tsv(pseudo_file, show_col_types = FALSE)
    )
  }
)

# Make a wide version as well
project_wide_df_list <- project_long_df_list |>
  purrr::map(
    \(df) {
      df |>
        tidyr::pivot_wider(names_from = expression_type, values_from = expression)
    }
)

We’ll read in the bulk counts data as well which have been normalized with DESeq2.

# we only want to keep these samples from the bulk counts
present_samples <- project_wide_df_list |>
  purrr::map(
    \(df) {
      df |> 
        dplyr::pull(sample_id) |> 
        unique()
    }
  ) |>
  purrr::reduce(c)

# Make a list of data frames of bulk counts, normalized by DESeq2
bulk_counts_wide_df_list <- readr::read_rds(bulk_counts_file) |>
  purrr::map(
    \(df) {
      df |>
        dplyr::filter(sample_id %in% present_samples) |>
        dplyr::rename(ensembl_id = gene_id)
    }
  )

Bind it up for later comparisons:

project_wide_df_list <- purrr::map2(
  bulk_counts_wide_df_list, 
  project_wide_df_list, 
  \(df_counts, df_main) {
    
    df_main |>
      dplyr::left_join(df_counts, by = c("ensembl_id", "sample_id"))
  }
)

Full distributions

First, we’ll visualize distributions of all quantities:

ggplot(purrr::list_rbind(project_long_df_list, names_to = "project_id")) + 
  aes(x = expression, fill = expression_type) + 
  geom_density(alpha = 0.5) + 
  scale_fill_brewer(palette = "Dark2") + 
  facet_grid(
    rows = vars(expression_type), 
    cols = vars(project_id),
    scales = "free_y"
  ) +
  theme(legend.position = "none")

We see big spikes at zero for pseudobulk, not surprisingly. Due to the different transformation approaches, the pseudobulk_deseq version has some negatives for fractional values, but the other quantities have a lower bound of zero. All around, distributions range from their lower bound up to around 20, so it’s nice to know pseudobulk and bulk are definitely on the same scale.

Relationship between quantities

This section will look at the relationship among quantities.

In this section, we’ll look at some scatterplots:

Compare pseudobulk measures

project_wide_df_list |>
  purrr::imap(
    \(df, project_id) {
      
      ggplot(df) + 
        aes(x = pseudobulk_deseq, y = pseudobulk_log_counts) + 
        geom_point(alpha = 0.2, size = 0.5) + 
        geom_smooth(method = "lm", linewidth = 0.5) +
        geom_abline(linewidth = 0.5, color = "red") + 
        facet_wrap(vars(sample_id), nrow = 5) +
        ggtitle(project_id) 

    }
  ) |>
  patchwork::wrap_plots(ncol = 1)

These quantities are exceptionally similar with these differences:

  • Driven by different normalization approaches, genes with very low to zero expression
  • In a handful of samples (1-2 per project), pseudobulk_log_counts appears to have a higher proportion of low to zero counts, and throughout has lower values than pseudobulk_deseq.

Compare pseudobulk to bulk counts

We’d like to also get a sense of how this data compares to counts directly.

# Helper function to visualize scatterplots with geom_bin_2d()
make_binned_scatterplots <- function(df, project_id, yvar, nbins, facet_rows) {
  p1 <- ggplot(df) + 
    aes(x = pseudobulk_deseq, y = {{yvar}}) + 
    geom_bin_2d(bins = nbins) + 
    geom_smooth(method = "lm", alpha = 0.8, linewidth = 0.5) +
    geom_abline(alpha = 0.8, linewidth = 0.5, color = "red") + 
    facet_wrap(vars(sample_id), nrow = facet_rows) +
    ggtitle("~ deseq") 
    
  p2 <- ggplot(df) + 
    aes(x = pseudobulk_log_counts, y = {{yvar}}) + 
    geom_bin_2d(bins = nbins) + 
    geom_smooth(method = "lm", alpha = 0.8, linewidth = 0.5) +
    geom_abline(alpha = 0.8, linewidth = 0.5, color = "red") + 
    facet_wrap(vars(sample_id), nrow = facet_rows) +
    ggtitle("~ log_counts") 
      
  print(
    patchwork::wrap_plots(p1, p2, ncol = 2) + patchwork::plot_annotation(title = project_id)
  )
}
make_binned_scatterplots(
  project_wide_df_list$SCPCP000001, 
  project_id = "SCPCP000001",
  yvar = bulk_counts,
  nbins = 15,
  facet_rows = 6
)

make_binned_scatterplots(
  project_wide_df_list$SCPCP000002, 
  project_id = "SCPCP000002",
  yvar = bulk_counts,
  nbins = 15,
  facet_rows = 6
)

make_binned_scatterplots(
  project_wide_df_list$SCPCP000006, 
  project_id = "SCPCP000006",
  yvar = bulk_counts,
  nbins = 15,
  facet_rows = 9
)

make_binned_scatterplots(
  project_wide_df_list$SCPCP000009, 
  project_id = "SCPCP000009",
  yvar = bulk_counts,
  nbins = 15,
  facet_rows = 1
)

make_binned_scatterplots(
  project_wide_df_list$SCPCP000017, 
  project_id = "SCPCP000017",
  yvar = bulk_counts,
  nbins = 40,
  facet_rows = 7
)

Statistics

Let’s now get some stats for the comparison between bulk counts and pseudobulk. We’ll fit a linear model for each sample, and display some quantities below both as boxplots and the full table.

# Helper functions to plot model statistics from data frame
plot_stats <- function(df, column, title) {
  ggplot(df) + 
    aes(x = expression_type, y = {{column}}, color = expression_type) + 
    geom_boxplot() + 
    scale_color_brewer(palette = "Dark2") +
    ggtitle(title) +
    facet_wrap(vars(project_id), nrow = 1) +
    theme(legend.position = "none")
}

model_samples_counts <- function(id, df) {
  sample_df <- df |>
    dplyr::filter(sample_id == id) 
  
  df_deseq <- sample_df |>
    dplyr::filter(is.finite(pseudobulk_deseq), is.finite(bulk_counts))
  fit_deseq <- summary(lm(bulk_counts ~ pseudobulk_deseq, data = df_deseq))

  df_log_counts <- sample_df |>
      dplyr::filter(is.finite(pseudobulk_log_counts), is.finite(bulk_counts))
  fit_log_counts <- summary(lm(bulk_counts ~ pseudobulk_log_counts, data = df_log_counts))
      
  # Tabulate and return some fit stats
  data.frame(
    expression_type = c("deseq", "log_counts"),
    rsquared = c(fit_deseq$r.squared, fit_log_counts$r.squared), 
    coeff = c(fit_deseq$coefficients[2], fit_log_counts$coefficients[2]), 
    residual_sd = c(fit_deseq$sigma,  fit_log_counts$sigma)
  )
}

stats_df <- project_wide_df_list |>
  purrr::map(
    \(df) {
      
      # We need to map over sample ids now
      samples <- unique(df$sample_id)
      names(samples) <- samples
      
      fit_table <- samples |>
        purrr::map(model_samples_counts, df) |>
        purrr::list_rbind(names_to = "sample_id")
      
      return(fit_table)

    }
  ) |>
  # now, combine all projects into a single table
  purrr::list_rbind(names_to = "project_id")

patchwork::wrap_plots(
  plot_stats(stats_df, rsquared, "rsquared"),
  plot_stats(stats_df, coeff, "coeff"), 
  plot_stats(stats_df, residual_sd, "residual_sd"), 
  nrow = 3
) 

  • Relationships between pseudobulk and bulk counts are exceptionally similar here, and further we are generally lying along the 1:1 line.
  • SCPCP000001 and SCPCP000002 have some outlying samples where log_counts performs worse, which are likely the cases we see in the scatterplots where the relationship with log_counts shows bias relative to y=x
  • Relationships are strongest forSCPCP000001 and SCPCP000002, then SCPCP000006, then SCPCP000009, and finally SCPCP000017 whose relationship is weak if at all present.
  • Samples within a given project have broadly similar coefficients, with a few outliers, suggesting less of an interaction among samples/expression than one might have thought. SCPCP000017 does have more variation here, but also the relationship is very weak in the first place so these different coefficients are not necessarily statistically significantly different.

All the actual values are here:

stats_df

Compare pseudobulk to bulk TPM

Now we’ll do the same for pseudobulk and bulk TPM

make_binned_scatterplots(
  project_wide_df_list$SCPCP000001, 
  project_id = "SCPCP000001",
  yvar = bulk_tpm,
  nbins = 40,
  facet_rows = 6
)

make_binned_scatterplots(
  project_wide_df_list$SCPCP000002, 
  project_id = "SCPCP000002",
  yvar = bulk_tpm,
  nbins = 30,
  facet_rows = 6
)

make_binned_scatterplots(
  project_wide_df_list$SCPCP000006, 
  project_id = "SCPCP000006",
  yvar = bulk_tpm,
  nbins = 50,
  facet_rows = 9
)

make_binned_scatterplots(
  project_wide_df_list$SCPCP000009, 
  project_id = "SCPCP000009",
  yvar = bulk_tpm,
  nbins = 15,
  facet_rows = 1
)

make_binned_scatterplots(
  project_wide_df_list$SCPCP000017, 
  project_id = "SCPCP000017",
  yvar = bulk_tpm,
  nbins = 40,
  facet_rows = 7
)

We see high concentrations of points around (0,0) as well as towards the middle values across plots. Next, we’ll look at regression stats for these plots directly.

Statistics

Let’s now get some stats for the comparison between bulk TPM and pseudobulk. We’ll fit a linear model for each sample, and display some quantities below both as boxplots and the full table.

model_samples_tpm <- function(id, df) {
  sample_df <- df |>
    dplyr::filter(sample_id == id) 
  
  df_deseq <- sample_df |>
    dplyr::filter(is.finite(pseudobulk_deseq), is.finite(bulk_tpm))
  fit_deseq <- summary(lm(bulk_tpm ~ pseudobulk_deseq, data = df_deseq))

  df_log_counts <- sample_df |>
      dplyr::filter(is.finite(pseudobulk_log_counts), is.finite(bulk_tpm))
  fit_log_counts <- summary(lm(bulk_tpm ~ pseudobulk_log_counts, data = df_log_counts))
      
  # Tabulate and return some fit stats
  data.frame(
    expression_type = c("deseq", "log_counts"),
    rsquared = c(fit_deseq$r.squared, fit_log_counts$r.squared), 
    coeff = c(fit_deseq$coefficients[2], fit_log_counts$coefficients[2]), 
    residual_sd = c(fit_deseq$sigma,  fit_log_counts$sigma)
  )
}

stats_df <- project_wide_df_list |>
  purrr::map(
    \(df) {
      
      # We need to map over sample ids now
      samples <- unique(df$sample_id)
      names(samples) <- samples
      
      fit_table <- samples |>
        purrr::map(model_samples_tpm, df) |>
        purrr::list_rbind(names_to = "sample_id")
      
      return(fit_table)

    }
  ) |>
  # now, combine all projects into a single table
  purrr::list_rbind(names_to = "project_id")

patchwork::wrap_plots(
  plot_stats(stats_df, rsquared, "rsquared"),
  plot_stats(stats_df, coeff, "coeff"), 
  plot_stats(stats_df, residual_sd, "residual_sd"), 
  nrow = 3
) 

  • Pseudobulk quantities are exceptionally similar here, which isn’t necessarily surprising given the similarity of the pseudobulk measures
  • Relationships are strongest forSCPCP000001 and SCPCP000002, then SCPCP000006, then SCPCP000009, and finally SCPCP000017 whose relationship is weak if at all present.
  • Samples within a given project have broadly similar coefficients, with a few outliers, suggesting less of an interaction among samples/expression than one might have thought. SCPCP000017 does have more variation here, but also the relationship is very weak in the first place so these different coefficients are not necessarily statistically significantly different.

All the actual values are here:

stats_df

Disagreeing expression

Currently this section does not include bulk counts, only bulk TPM.

Next, we’ll take a quick look at cases where one modality has zero expression and the other doesn’t. In these cases, if expression is generally high, we have evidence of disagreement/discrepancy between bulk and single-cell that may be interesting to investigate. In this notebook, we’ll just a sense of how much “there is there,” and we’ll leave the in-depth look into any such genes for a subsequent notebook.

In this section, we’ll also use a threshold of 1e-12 for zero here.

Bulk TPM when single-cell is zero

project_wide_df_list |>
  purrr::map(
    \(df) {
      
      low_deseq <- df |>
        dplyr::filter(pseudobulk_deseq <= 1e-12, 
                      bulk_tpm > 1e-12)
      low_logcounts <- df |>
        dplyr::filter(pseudobulk_log_counts <= 1e-12, 
                      bulk_tpm > 1e-12)  

      
      p1 <- ggplot(low_deseq) + 
        aes(x = sample_id, y = bulk_tpm) + 
        ggforce::geom_sina(size = 0.5) +
        theme(axis.text.x = element_blank()) +
        ggtitle("TPM for zero & negative pseudobulk_deseq")
  
      p2 <- ggplot(low_logcounts) + 
        aes(x = sample_id, y = bulk_tpm) + 
        ggforce::geom_sina(size = 0.5) +
        theme(axis.text.x = element_blank()) +
        ggtitle("TPM for zero pseudobulk_log_counts")
      
      patchwork::wrap_plots(p1, p2, nrow = 1)
    }
  ) |>
  patchwork::wrap_plots(ncol = 1)

Single-cell when bulk TPM is zero

project_wide_df_list |>
  purrr::imap(
    \(df, project_id) {
      
      low_bulk <- df |>
        # Even though we don't have 1:1 correspondence between pseudobulks here,
        #  we'll just consider only points where neither is 0 to get a sense.
        dplyr::filter(bulk_tpm <= 1e-12, 
                      pseudobulk_deseq> 1e-12, 
                      pseudobulk_log_counts> 1e-12) |>
        tidyr::pivot_longer(
          contains("pseudobulk"), 
          names_to = "expression_type", 
          values_to = "expression"
        )
      
      ggplot(low_bulk) + 
        aes(x = sample_id, y = expression, color = expression_type) + 
        ggforce::geom_sina(size = 0.5) +
        theme(axis.text.x = element_blank()) +
        ggtitle(project_id)

    }
  ) |>
  patchwork::wrap_plots(ncol = 1, guides = "collect")

From both comparisons, there are a fair number of genes with high expression in one modality and essentially zero in the other. A more careful investigation here look into what exactly these genes are, and whether they have some biological relationship that might suggest modalities are picking up different information.

Do pseudobulks have strong disagreements?

This will show genes strongly affected by different preparation/normalization approaches.

project_wide_df_list |>
  purrr::imap(
    \(df, project_id) {
      
      different_low_pseudo <- df |>
        dplyr::filter((pseudobulk_deseq> 1e-12 & pseudobulk_log_counts <= 1e-12) |
                       (pseudobulk_deseq <=1e-12 & pseudobulk_log_counts > 1e-12) ) |>
        tidyr::pivot_longer(
          contains("pseudobulk"), 
          names_to = "expression_type", 
          values_to = "expression"
        )
      
      ggplot(different_low_pseudo) + 
        aes(x = sample_id, y = expression, color = expression_type) + 
        ggforce::geom_sina(size = 0.5) +
        theme(axis.text.x = element_blank()) +
        ggtitle(project_id)

    }
  ) |>
  patchwork::wrap_plots(ncol = 1, guides = "collect")

Most values seem to be [-2.5, 2.5] for both pseudobulks, but some are getting as high as 6-9.

Session info

sessionInfo()
R version 4.4.0 (2024-04-24)
Platform: aarch64-apple-darwin20
Running under: macOS 15.3

Matrix products: default
BLAS:   /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRblas.0.dylib 
LAPACK: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.0

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

time zone: America/New_York
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] ggplot2_3.5.1

loaded via a namespace (and not attached):
 [1] sass_0.4.9          generics_0.1.3      tidyr_1.3.1        
 [4] renv_1.0.11         stringi_1.8.4       lattice_0.22-6     
 [7] hms_1.1.3           digest_0.6.37       magrittr_2.0.3     
[10] evaluate_1.0.1      grid_4.4.0          RColorBrewer_1.1-3 
[13] fastmap_1.2.0       Matrix_1.7-1        rprojroot_2.0.4    
[16] jsonlite_1.8.9      BiocManager_1.30.25 mgcv_1.9-1         
[19] purrr_1.0.2         scales_1.3.0        tweenr_2.0.3       
[22] jquerylib_0.1.4     cli_3.6.3           rlang_1.1.4        
[25] crayon_1.5.3        polyclip_1.10-7     bit64_4.5.2        
[28] munsell_0.5.1       splines_4.4.0       withr_3.0.2        
[31] cachem_1.1.0        yaml_2.3.10         tools_4.4.0        
[34] parallel_4.4.0      tzdb_0.4.0          dplyr_1.1.4        
[37] colorspace_2.1-1    here_1.0.1          vctrs_0.6.5        
[40] R6_2.5.1            lifecycle_1.0.4     stringr_1.5.1      
[43] bit_4.5.0.1         MASS_7.3-64         vroom_1.6.5        
[46] pkgconfig_2.0.3     pillar_1.10.0       bslib_0.8.0        
[49] gtable_0.3.6        Rcpp_1.0.13-1       glue_1.8.0         
[52] ggforce_0.4.2       xfun_0.49           tibble_3.2.1       
[55] tidyselect_1.2.1    knitr_1.49          farver_2.1.2       
[58] htmltools_0.5.8.1   nlme_3.1-166        patchwork_1.3.0    
[61] rmarkdown_2.29      labeling_0.4.3      readr_2.1.5        
[64] compiler_4.4.0     
LS0tCnRpdGxlOiAiSW5pdGlhbCBleHBsb3JhdGlvbiBhbmQgY29tcGFyaXNvbiBvZiBwc2V1ZG9idWxrIHZzLiBidWxrIGV4cHJlc3Npb24iCmF1dGhvcjogU3RlcGhhbmllIEouIFNwaWVsbWFuCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHRydWUKICAgIHRvY19kZXB0aDogMwogICAgY29kZV9mb2xkaW5nOiBoaWRlCi0tLQoKVGhlIGdvYWwgb2YgdGhpcyBub3RlYm9vayBpcyB0byBjb21wYXJlIHBzZXVkb2J1bGsgYW5kIGJ1bGsgY2FsY3VsYXRpb25zIHRvIGRldGVybWluZSB3aGljaCBwc2V1ZG9idWxrIGNhbGN1bGF0aW9uIHNob3VsZCB3ZSBwcm9jZWVkIHdpdGggZm9yIG1vZGVsaW5nOiB0aGUgbG9nMiBvZiB0aGUgc3VtIG9mIHJhdyBjb3VudHMgKGBwc2V1ZG9idWxrX2xvZ19jb3VudHNgKSBvciB0aGUgYERFU2VxMmAtbm9ybWFsaXplZCBzdW0gb2YgcmF3IGNvdW50cyAoYHBzZXVkb2J1bGtfZGVzZXFgKS4KVG8gdGhpcyBlbmQsIHdlJ2xsIGV4cGxvcmUgcHNldWRvYnVsayBleHByZXNzaW9uIGRpc3RyaWJ1dGlvbnMsIGNvbXBhcmUgdGhlbSB0byBidWxrLCBhbmQgYWxzbyBleHBsb3JlIGRpc3RyaWJ1dGlvbnMgb2YgZXhwcmVzc2lvbiB3aGVyZSB0aGVyZSBpcyBkaXNhZ3JlZW1lbnQgYmV0d2VlbiBidWxrIGFuZCBzaW5nbGUtY2VsbC4gCldlIGFsc28gY29tcGFyZSB0byBidWxrIGNvdW50cyB0byBzZWUgd2hpY2ggcXVhbnRpdHkgbW9yZSBjbG9zZWx5IGFwcHJveGltYXRlcyB0aG9zZSB2YWx1ZXMuCgoKIyMgU2V0dXAKCmBgYHtyIHNldHVwfQpyZW52Ojpsb2FkKCkKCmxpYnJhcnkoZ2dwbG90MikKdGhlbWVfc2V0KHRoZW1lX2J3KCkpCmBgYAoKIyMjIFBhdGhzCgpgYGB7ciBwYXRoc30KZGF0YV9kaXIgPC0gaGVyZTo6aGVyZSgiYW5hbHlzaXMiLCAicHNldWRvYnVsay1idWxrLXByZWRpY3Rpb24iLCAiZGF0YSIpCnRwbV9kaXIgPC0gZmlsZS5wYXRoKGRhdGFfZGlyLCAidHBtIikKcHNldWRvYnVsa19kaXIgPC0gZmlsZS5wYXRoKGRhdGFfZGlyLCAicHNldWRvYnVsayIpCmJ1bGtfY291bnRzX2ZpbGUgPC0gZmlsZS5wYXRoKGRhdGFfZGlyLCAic2NwY2FfZGF0YSIsICJub3JtYWxpemVkX2J1bGtfY291bnRzLnJkcyIpCgp0cG1fZmlsZXMgPC0gbGlzdC5maWxlcygKICBwYXRoID0gdHBtX2RpciwKICBmdWxsLm5hbWVzID0gVFJVRSwKICBwYXR0ZXJuID0gIi10cG1cXC50c3YkIgopCnRwbV9uYW1lcyA8LSBzdHJpbmdyOjpzdHJfc3BsaXRfaShiYXNlbmFtZSh0cG1fZmlsZXMpLCBwYXR0ZXJuID0gIi0iLCBpID0gMSkKbmFtZXModHBtX2ZpbGVzKSA8LSB0cG1fbmFtZXMKCgpwc2V1ZG9idWxrX2ZpbGVzIDwtIGxpc3QuZmlsZXMoCiAgcGF0aCA9IHBzZXVkb2J1bGtfZGlyLAogIGZ1bGwubmFtZXMgPSBUUlVFLAogIHBhdHRlcm4gPSAiLXBzZXVkb2J1bGtcXC50c3YkIgopCnBzZXVkb2J1bGtfbmFtZXMgPC0gc3RyaW5ncjo6c3RyX3NwbGl0X2koYmFzZW5hbWUocHNldWRvYnVsa19maWxlcyksIHBhdHRlcm4gPSAiLSIsIGkgPSAxKQpuYW1lcyhwc2V1ZG9idWxrX2ZpbGVzKSA8LSBwc2V1ZG9idWxrX25hbWVzCgojIE1ha2Ugc3VyZSB3ZSBoYXZlIHRoZSBzYW1lIHByb2plY3RzLCBpbiB0aGUgc2FtZSBvcmRlcgpzdG9waWZub3QoCiAgYWxsLmVxdWFsKG5hbWVzKHRwbV9maWxlcyksIG5hbWVzKHBzZXVkb2J1bGtfZmlsZXMpKQopCmBgYAoKIyMjIFJlYWQgYW5kIHByZXBhcmUgaW5wdXQgZGF0YQoKV2UnbGwgbWFrZSBib3RoIGEgbG9uZyBhbmQgd2lkZSB2ZXJzaW9uIG9mIHRoZSBkYXRhIGZvciBjb252ZW5pZW5jZSB0aHJvdWdob3V0IHRoZSBub3RlYm9vay4KCgpgYGB7cn0KcHJvamVjdF9sb25nX2RmX2xpc3QgPC0gcHVycnI6Om1hcDIoCiAgdHBtX2ZpbGVzLCAKICBwc2V1ZG9idWxrX2ZpbGVzLCAKICBcKHRwbV9maWxlLCBwc2V1ZG9fZmlsZSkgewogICAgCiAgICBkcGx5cjo6YmluZF9yb3dzKAogICAgICAjIFRQTSBuZWVkcyB0byBiZSBpbiBsb2cyIHNwYWNlCiAgICAgIHJlYWRyOjpyZWFkX3Rzdih0cG1fZmlsZSwgc2hvd19jb2xfdHlwZXMgPSBGQUxTRSkgfD4KICAgICAgICBkcGx5cjo6bXV0YXRlKGV4cHJlc3Npb24gPSBsb2cyKGV4cHJlc3Npb24pKSwgCiAgICAgIHJlYWRyOjpyZWFkX3Rzdihwc2V1ZG9fZmlsZSwgc2hvd19jb2xfdHlwZXMgPSBGQUxTRSkKICAgICkKICB9CikKCiMgTWFrZSBhIHdpZGUgdmVyc2lvbiBhcyB3ZWxsCnByb2plY3Rfd2lkZV9kZl9saXN0IDwtIHByb2plY3RfbG9uZ19kZl9saXN0IHw+CiAgcHVycnI6Om1hcCgKICAgIFwoZGYpIHsKICAgICAgZGYgfD4KICAgICAgICB0aWR5cjo6cGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IGV4cHJlc3Npb25fdHlwZSwgdmFsdWVzX2Zyb20gPSBleHByZXNzaW9uKQogICAgfQopCmBgYAoKV2UnbGwgcmVhZCBpbiB0aGUgYnVsayBjb3VudHMgZGF0YSBhcyB3ZWxsIHdoaWNoIGhhdmUgYmVlbiBub3JtYWxpemVkIHdpdGggREVTZXEyLgoKYGBge3J9CiMgd2Ugb25seSB3YW50IHRvIGtlZXAgdGhlc2Ugc2FtcGxlcyBmcm9tIHRoZSBidWxrIGNvdW50cwpwcmVzZW50X3NhbXBsZXMgPC0gcHJvamVjdF93aWRlX2RmX2xpc3QgfD4KICBwdXJycjo6bWFwKAogICAgXChkZikgewogICAgICBkZiB8PiAKICAgICAgICBkcGx5cjo6cHVsbChzYW1wbGVfaWQpIHw+IAogICAgICAgIHVuaXF1ZSgpCiAgICB9CiAgKSB8PgogIHB1cnJyOjpyZWR1Y2UoYykKCiMgTWFrZSBhIGxpc3Qgb2YgZGF0YSBmcmFtZXMgb2YgYnVsayBjb3VudHMsIG5vcm1hbGl6ZWQgYnkgREVTZXEyCmJ1bGtfY291bnRzX3dpZGVfZGZfbGlzdCA8LSByZWFkcjo6cmVhZF9yZHMoYnVsa19jb3VudHNfZmlsZSkgfD4KICBwdXJycjo6bWFwKAogICAgXChkZikgewogICAgICBkZiB8PgogICAgICAgIGRwbHlyOjpmaWx0ZXIoc2FtcGxlX2lkICVpbiUgcHJlc2VudF9zYW1wbGVzKSB8PgogICAgICAgIGRwbHlyOjpyZW5hbWUoZW5zZW1ibF9pZCA9IGdlbmVfaWQpCiAgICB9CiAgKQpgYGAKCgpCaW5kIGl0IHVwIGZvciBsYXRlciBjb21wYXJpc29uczoKCmBgYHtyfQpwcm9qZWN0X3dpZGVfZGZfbGlzdCA8LSBwdXJycjo6bWFwMigKICBidWxrX2NvdW50c193aWRlX2RmX2xpc3QsIAogIHByb2plY3Rfd2lkZV9kZl9saXN0LCAKICBcKGRmX2NvdW50cywgZGZfbWFpbikgewogICAgCiAgICBkZl9tYWluIHw+CiAgICAgIGRwbHlyOjpsZWZ0X2pvaW4oZGZfY291bnRzLCBieSA9IGMoImVuc2VtYmxfaWQiLCAic2FtcGxlX2lkIikpCiAgfQopCmBgYAoKCiMjIEZ1bGwgZGlzdHJpYnV0aW9ucwoKRmlyc3QsIHdlJ2xsIHZpc3VhbGl6ZSBkaXN0cmlidXRpb25zIG9mIGFsbCBxdWFudGl0aWVzOgoKYGBge3IsIGZpZy53aWR0aCA9IDcsIGZpZy5oZWlnaHQgPSA1LCB3YXJuaW5nID0gRkFMU0V9CmdncGxvdChwdXJycjo6bGlzdF9yYmluZChwcm9qZWN0X2xvbmdfZGZfbGlzdCwgbmFtZXNfdG8gPSAicHJvamVjdF9pZCIpKSArIAogIGFlcyh4ID0gZXhwcmVzc2lvbiwgZmlsbCA9IGV4cHJlc3Npb25fdHlwZSkgKyAKICBnZW9tX2RlbnNpdHkoYWxwaGEgPSAwLjUpICsgCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJEYXJrMiIpICsgCiAgZmFjZXRfZ3JpZCgKICAgIHJvd3MgPSB2YXJzKGV4cHJlc3Npb25fdHlwZSksIAogICAgY29scyA9IHZhcnMocHJvamVjdF9pZCksCiAgICBzY2FsZXMgPSAiZnJlZV95IgogICkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKYGBgCgpXZSBzZWUgYmlnIHNwaWtlcyBhdCB6ZXJvIGZvciBwc2V1ZG9idWxrLCBub3Qgc3VycHJpc2luZ2x5LgpEdWUgdG8gdGhlIGRpZmZlcmVudCB0cmFuc2Zvcm1hdGlvbiBhcHByb2FjaGVzLCB0aGUgYHBzZXVkb2J1bGtfZGVzZXFgIHZlcnNpb24gaGFzIHNvbWUgbmVnYXRpdmVzIGZvciBmcmFjdGlvbmFsIHZhbHVlcywgYnV0IHRoZSBvdGhlciBxdWFudGl0aWVzIGhhdmUgYSBsb3dlciBib3VuZCBvZiB6ZXJvLgpBbGwgYXJvdW5kLCBkaXN0cmlidXRpb25zIHJhbmdlIGZyb20gdGhlaXIgbG93ZXIgYm91bmQgdXAgdG8gYXJvdW5kIDIwLCBzbyBpdCdzIG5pY2UgdG8ga25vdyBwc2V1ZG9idWxrIGFuZCBidWxrIGFyZSBkZWZpbml0ZWx5IG9uIHRoZSBzYW1lIHNjYWxlLgoKICAKIyMgUmVsYXRpb25zaGlwIGJldHdlZW4gcXVhbnRpdGllcwoKVGhpcyBzZWN0aW9uIHdpbGwgbG9vayBhdCB0aGUgcmVsYXRpb25zaGlwIGFtb25nIHF1YW50aXRpZXMuCgpJbiB0aGlzIHNlY3Rpb24sIHdlJ2xsIGxvb2sgYXQgc29tZSBzY2F0dGVycGxvdHM6CgoqIEhvdyBzaW1pbGFyIGFyZSB0aGUgcHNldWRvYnVsayBtZWFzdXJlcyB0aGVtc2VsdmVzPwoqIEhvdyBkb2VzIGVhY2ggcHNldWRvYnVsayBtZWFzdXJlIGNvbXBhcmUgdG8gYnVsayBUUE0/CiAgKiBGb3IgdGhlc2UgcGxvdHMsIHdlJ2xsIGJpbiBkYXRhIHRvIHNlZSB0aGUgY29uY2VudHJhdGlvbiBvZiBvdmVybGFwcGluZyBwb2ludHMgbW9yZSBlYXNpbHkuCiogSG93IGRvZXMgZWFjaCBwc2V1ZG9idWxrIG1lYXN1cmUgY29tcGFyZSB0byBidWxrIG5vcm1hbGl6ZWQgY291bnRzIChub3QgVFBNKT8KICAqIEZvciB0aGVzZSBwbG90cywgd2UnbGwgYmluIGRhdGEgdG8gc2VlIHRoZSBjb25jZW50cmF0aW9uIG9mIG92ZXJsYXBwaW5nIHBvaW50cyBtb3JlIGVhc2lseS4KSW4gYWxsIHBsb3RzLCB0aGUgcmVkIGxpbmUgaXMgYHk9eGAsIGFuZCB0aGUgYmx1ZSBsaW5lIGlzIHRoZSByZWdyZXNzaW9uIGxpbmUuCgoKIyMjIENvbXBhcmUgcHNldWRvYnVsayBtZWFzdXJlcwoKYGBge3IgZmlnLmhlaWdodD0zNCwgZmlnLndpZHRoPTgsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnByb2plY3Rfd2lkZV9kZl9saXN0IHw+CiAgcHVycnI6OmltYXAoCiAgICBcKGRmLCBwcm9qZWN0X2lkKSB7CiAgICAgIAogICAgICBnZ3Bsb3QoZGYpICsgCiAgICAgICAgYWVzKHggPSBwc2V1ZG9idWxrX2Rlc2VxLCB5ID0gcHNldWRvYnVsa19sb2dfY291bnRzKSArIAogICAgICAgIGdlb21fcG9pbnQoYWxwaGEgPSAwLjIsIHNpemUgPSAwLjUpICsgCiAgICAgICAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgbGluZXdpZHRoID0gMC41KSArCiAgICAgICAgZ2VvbV9hYmxpbmUobGluZXdpZHRoID0gMC41LCBjb2xvciA9ICJyZWQiKSArIAogICAgICAgIGZhY2V0X3dyYXAodmFycyhzYW1wbGVfaWQpLCBucm93ID0gNSkgKwogICAgICAgIGdndGl0bGUocHJvamVjdF9pZCkgCgogICAgfQogICkgfD4KICBwYXRjaHdvcms6OndyYXBfcGxvdHMobmNvbCA9IDEpCmBgYAoKClRoZXNlIHF1YW50aXRpZXMgYXJlIGV4Y2VwdGlvbmFsbHkgc2ltaWxhciB3aXRoIHRoZXNlIGRpZmZlcmVuY2VzOgoKKiBEcml2ZW4gYnkgZGlmZmVyZW50IG5vcm1hbGl6YXRpb24gYXBwcm9hY2hlcywgZ2VuZXMgd2l0aCB2ZXJ5IGxvdyB0byB6ZXJvIGV4cHJlc3Npb24KKiBJbiBhIGhhbmRmdWwgb2Ygc2FtcGxlcyAoMS0yIHBlciBwcm9qZWN0KSwgYHBzZXVkb2J1bGtfbG9nX2NvdW50c2AgYXBwZWFycyB0byBoYXZlIGEgaGlnaGVyIHByb3BvcnRpb24gb2YgbG93IHRvIHplcm8gY291bnRzLCBhbmQgdGhyb3VnaG91dCBoYXMgbG93ZXIgdmFsdWVzIHRoYW4gYHBzZXVkb2J1bGtfZGVzZXFgLgoKCiMjIyBDb21wYXJlIHBzZXVkb2J1bGsgdG8gYnVsayBjb3VudHMKCldlJ2QgbGlrZSB0byBhbHNvIGdldCBhIHNlbnNlIG9mIGhvdyB0aGlzIGRhdGEgY29tcGFyZXMgdG8gY291bnRzIGRpcmVjdGx5LgoKCmBgYHtyfQojIEhlbHBlciBmdW5jdGlvbiB0byB2aXN1YWxpemUgc2NhdHRlcnBsb3RzIHdpdGggZ2VvbV9iaW5fMmQoKQptYWtlX2Jpbm5lZF9zY2F0dGVycGxvdHMgPC0gZnVuY3Rpb24oZGYsIHByb2plY3RfaWQsIHl2YXIsIG5iaW5zLCBmYWNldF9yb3dzKSB7CiAgcDEgPC0gZ2dwbG90KGRmKSArIAogICAgYWVzKHggPSBwc2V1ZG9idWxrX2Rlc2VxLCB5ID0ge3t5dmFyfX0pICsgCiAgICBnZW9tX2Jpbl8yZChiaW5zID0gbmJpbnMpICsgCiAgICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBhbHBoYSA9IDAuOCwgbGluZXdpZHRoID0gMC41KSArCiAgICBnZW9tX2FibGluZShhbHBoYSA9IDAuOCwgbGluZXdpZHRoID0gMC41LCBjb2xvciA9ICJyZWQiKSArIAogICAgZmFjZXRfd3JhcCh2YXJzKHNhbXBsZV9pZCksIG5yb3cgPSBmYWNldF9yb3dzKSArCiAgICBnZ3RpdGxlKCJ+IGRlc2VxIikgCiAgICAKICBwMiA8LSBnZ3Bsb3QoZGYpICsgCiAgICBhZXMoeCA9IHBzZXVkb2J1bGtfbG9nX2NvdW50cywgeSA9IHt7eXZhcn19KSArIAogICAgZ2VvbV9iaW5fMmQoYmlucyA9IG5iaW5zKSArIAogICAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgYWxwaGEgPSAwLjgsIGxpbmV3aWR0aCA9IDAuNSkgKwogICAgZ2VvbV9hYmxpbmUoYWxwaGEgPSAwLjgsIGxpbmV3aWR0aCA9IDAuNSwgY29sb3IgPSAicmVkIikgKyAKICAgIGZhY2V0X3dyYXAodmFycyhzYW1wbGVfaWQpLCBucm93ID0gZmFjZXRfcm93cykgKwogICAgZ2d0aXRsZSgifiBsb2dfY291bnRzIikgCiAgICAgIAogIHByaW50KAogICAgcGF0Y2h3b3JrOjp3cmFwX3Bsb3RzKHAxLCBwMiwgbmNvbCA9IDIpICsgcGF0Y2h3b3JrOjpwbG90X2Fubm90YXRpb24odGl0bGUgPSBwcm9qZWN0X2lkKQogICkKfQpgYGAKCgoKCmBgYHtyIGZpZy5oZWlnaHQgPSAxMiwgZmlnLndpZHRoID0gMTIsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9Cm1ha2VfYmlubmVkX3NjYXR0ZXJwbG90cygKICBwcm9qZWN0X3dpZGVfZGZfbGlzdCRTQ1BDUDAwMDAwMSwgCiAgcHJvamVjdF9pZCA9ICJTQ1BDUDAwMDAwMSIsCiAgeXZhciA9IGJ1bGtfY291bnRzLAogIG5iaW5zID0gMTUsCiAgZmFjZXRfcm93cyA9IDYKKQpgYGAKCgoKYGBge3IgZmlnLmhlaWdodCA9IDEwLCBmaWcud2lkdGggPSAxMiwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KbWFrZV9iaW5uZWRfc2NhdHRlcnBsb3RzKAogIHByb2plY3Rfd2lkZV9kZl9saXN0JFNDUENQMDAwMDAyLCAKICBwcm9qZWN0X2lkID0gIlNDUENQMDAwMDAyIiwKICB5dmFyID0gYnVsa19jb3VudHMsCiAgbmJpbnMgPSAxNSwKICBmYWNldF9yb3dzID0gNgopCmBgYAoKCmBgYHtyIGZpZy5oZWlnaHQgPSAxMiwgZmlnLndpZHRoID0gMTIsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9Cm1ha2VfYmlubmVkX3NjYXR0ZXJwbG90cygKICBwcm9qZWN0X3dpZGVfZGZfbGlzdCRTQ1BDUDAwMDAwNiwgCiAgcHJvamVjdF9pZCA9ICJTQ1BDUDAwMDAwNiIsCiAgeXZhciA9IGJ1bGtfY291bnRzLAogIG5iaW5zID0gMTUsCiAgZmFjZXRfcm93cyA9IDkKKQpgYGAKCgpgYGB7ciBmaWcuaGVpZ2h0ID0gMywgZmlnLndpZHRoID0gMTIsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9Cm1ha2VfYmlubmVkX3NjYXR0ZXJwbG90cygKICBwcm9qZWN0X3dpZGVfZGZfbGlzdCRTQ1BDUDAwMDAwOSwgCiAgcHJvamVjdF9pZCA9ICJTQ1BDUDAwMDAwOSIsCiAgeXZhciA9IGJ1bGtfY291bnRzLAogIG5iaW5zID0gMTUsCiAgZmFjZXRfcm93cyA9IDEKKQpgYGAKCgpgYGB7ciBmaWcuaGVpZ2h0ID0gOCwgZmlnLndpZHRoID0gMTIsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9Cm1ha2VfYmlubmVkX3NjYXR0ZXJwbG90cygKICBwcm9qZWN0X3dpZGVfZGZfbGlzdCRTQ1BDUDAwMDAxNywgCiAgcHJvamVjdF9pZCA9ICJTQ1BDUDAwMDAxNyIsCiAgeXZhciA9IGJ1bGtfY291bnRzLAogIG5iaW5zID0gNDAsCiAgZmFjZXRfcm93cyA9IDcKKQpgYGAKCgoKCiMjIyMgU3RhdGlzdGljcwoKTGV0J3Mgbm93IGdldCBzb21lIHN0YXRzIGZvciB0aGUgY29tcGFyaXNvbiBiZXR3ZWVuIGJ1bGsgY291bnRzIGFuZCBwc2V1ZG9idWxrLgpXZSdsbCBmaXQgYSBsaW5lYXIgbW9kZWwgZm9yIGVhY2ggc2FtcGxlLCBhbmQgZGlzcGxheSBzb21lIHF1YW50aXRpZXMgYmVsb3cgYm90aCBhcyBib3hwbG90cyBhbmQgdGhlIGZ1bGwgdGFibGUuCgpgYGB7ciBmaWcuaGVpZ2h0ID0gMTAsIGZpZy53aWR0aCA9IDEyfQojIEhlbHBlciBmdW5jdGlvbnMgdG8gcGxvdCBtb2RlbCBzdGF0aXN0aWNzIGZyb20gZGF0YSBmcmFtZQpwbG90X3N0YXRzIDwtIGZ1bmN0aW9uKGRmLCBjb2x1bW4sIHRpdGxlKSB7CiAgZ2dwbG90KGRmKSArIAogICAgYWVzKHggPSBleHByZXNzaW9uX3R5cGUsIHkgPSB7e2NvbHVtbn19LCBjb2xvciA9IGV4cHJlc3Npb25fdHlwZSkgKyAKICAgIGdlb21fYm94cGxvdCgpICsgCiAgICBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZSA9ICJEYXJrMiIpICsKICAgIGdndGl0bGUodGl0bGUpICsKICAgIGZhY2V0X3dyYXAodmFycyhwcm9qZWN0X2lkKSwgbnJvdyA9IDEpICsKICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKfQoKbW9kZWxfc2FtcGxlc19jb3VudHMgPC0gZnVuY3Rpb24oaWQsIGRmKSB7CiAgc2FtcGxlX2RmIDwtIGRmIHw+CiAgICBkcGx5cjo6ZmlsdGVyKHNhbXBsZV9pZCA9PSBpZCkgCiAgCiAgZGZfZGVzZXEgPC0gc2FtcGxlX2RmIHw+CiAgICBkcGx5cjo6ZmlsdGVyKGlzLmZpbml0ZShwc2V1ZG9idWxrX2Rlc2VxKSwgaXMuZmluaXRlKGJ1bGtfY291bnRzKSkKICBmaXRfZGVzZXEgPC0gc3VtbWFyeShsbShidWxrX2NvdW50cyB+IHBzZXVkb2J1bGtfZGVzZXEsIGRhdGEgPSBkZl9kZXNlcSkpCgogIGRmX2xvZ19jb3VudHMgPC0gc2FtcGxlX2RmIHw+CiAgICAgIGRwbHlyOjpmaWx0ZXIoaXMuZmluaXRlKHBzZXVkb2J1bGtfbG9nX2NvdW50cyksIGlzLmZpbml0ZShidWxrX2NvdW50cykpCiAgZml0X2xvZ19jb3VudHMgPC0gc3VtbWFyeShsbShidWxrX2NvdW50cyB+IHBzZXVkb2J1bGtfbG9nX2NvdW50cywgZGF0YSA9IGRmX2xvZ19jb3VudHMpKQogICAgICAKICAjIFRhYnVsYXRlIGFuZCByZXR1cm4gc29tZSBmaXQgc3RhdHMKICBkYXRhLmZyYW1lKAogICAgZXhwcmVzc2lvbl90eXBlID0gYygiZGVzZXEiLCAibG9nX2NvdW50cyIpLAogICAgcnNxdWFyZWQgPSBjKGZpdF9kZXNlcSRyLnNxdWFyZWQsIGZpdF9sb2dfY291bnRzJHIuc3F1YXJlZCksIAogICAgY29lZmYgPSBjKGZpdF9kZXNlcSRjb2VmZmljaWVudHNbMl0sIGZpdF9sb2dfY291bnRzJGNvZWZmaWNpZW50c1syXSksIAogICAgcmVzaWR1YWxfc2QgPSBjKGZpdF9kZXNlcSRzaWdtYSwgIGZpdF9sb2dfY291bnRzJHNpZ21hKQogICkKfQoKc3RhdHNfZGYgPC0gcHJvamVjdF93aWRlX2RmX2xpc3QgfD4KICBwdXJycjo6bWFwKAogICAgXChkZikgewogICAgICAKICAgICAgIyBXZSBuZWVkIHRvIG1hcCBvdmVyIHNhbXBsZSBpZHMgbm93CiAgICAgIHNhbXBsZXMgPC0gdW5pcXVlKGRmJHNhbXBsZV9pZCkKICAgICAgbmFtZXMoc2FtcGxlcykgPC0gc2FtcGxlcwogICAgICAKICAgICAgZml0X3RhYmxlIDwtIHNhbXBsZXMgfD4KICAgICAgICBwdXJycjo6bWFwKG1vZGVsX3NhbXBsZXNfY291bnRzLCBkZikgfD4KICAgICAgICBwdXJycjo6bGlzdF9yYmluZChuYW1lc190byA9ICJzYW1wbGVfaWQiKQogICAgICAKICAgICAgcmV0dXJuKGZpdF90YWJsZSkKCiAgICB9CiAgKSB8PgogICMgbm93LCBjb21iaW5lIGFsbCBwcm9qZWN0cyBpbnRvIGEgc2luZ2xlIHRhYmxlCiAgcHVycnI6Omxpc3RfcmJpbmQobmFtZXNfdG8gPSAicHJvamVjdF9pZCIpCgpwYXRjaHdvcms6OndyYXBfcGxvdHMoCiAgcGxvdF9zdGF0cyhzdGF0c19kZiwgcnNxdWFyZWQsICJyc3F1YXJlZCIpLAogIHBsb3Rfc3RhdHMoc3RhdHNfZGYsIGNvZWZmLCAiY29lZmYiKSwgCiAgcGxvdF9zdGF0cyhzdGF0c19kZiwgcmVzaWR1YWxfc2QsICJyZXNpZHVhbF9zZCIpLCAKICBucm93ID0gMwopIApgYGAKCiogUmVsYXRpb25zaGlwcyBiZXR3ZWVuIHBzZXVkb2J1bGsgYW5kIGJ1bGsgY291bnRzIGFyZSBleGNlcHRpb25hbGx5IHNpbWlsYXIgaGVyZSwgYW5kIGZ1cnRoZXIgd2UgYXJlIGdlbmVyYWxseSBseWluZyBhbG9uZyB0aGUgMToxIGxpbmUuCiogYFNDUENQMDAwMDAxYCBhbmQgYFNDUENQMDAwMDAyYCBoYXZlIHNvbWUgb3V0bHlpbmcgc2FtcGxlcyB3aGVyZSBgbG9nX2NvdW50c2AgcGVyZm9ybXMgd29yc2UsIHdoaWNoIGFyZSBsaWtlbHkgdGhlIGNhc2VzIHdlIHNlZSBpbiB0aGUgc2NhdHRlcnBsb3RzIHdoZXJlIHRoZSByZWxhdGlvbnNoaXAgd2l0aCBgbG9nX2NvdW50c2Agc2hvd3MgYmlhcyByZWxhdGl2ZSB0byBgeT14YAoqIFJlbGF0aW9uc2hpcHMgYXJlIHN0cm9uZ2VzdCBmb3JgU0NQQ1AwMDAwMDFgIGFuZCBgU0NQQ1AwMDAwMDJgLCB0aGVuIGBTQ1BDUDAwMDAwNmAsIHRoZW4gYFNDUENQMDAwMDA5YCwgYW5kIGZpbmFsbHkgYFNDUENQMDAwMDE3YCB3aG9zZSByZWxhdGlvbnNoaXAgaXMgd2VhayBpZiBhdCBhbGwgcHJlc2VudC4KKiBTYW1wbGVzIHdpdGhpbiBhIGdpdmVuIHByb2plY3QgaGF2ZSBicm9hZGx5IHNpbWlsYXIgY29lZmZpY2llbnRzLCB3aXRoIGEgZmV3IG91dGxpZXJzLCBzdWdnZXN0aW5nIGxlc3Mgb2YgYW4gaW50ZXJhY3Rpb24gYW1vbmcgc2FtcGxlcy9leHByZXNzaW9uIHRoYW4gb25lIG1pZ2h0IGhhdmUgdGhvdWdodC4KYFNDUENQMDAwMDE3YCBkb2VzIGhhdmUgbW9yZSB2YXJpYXRpb24gaGVyZSwgYnV0IGFsc28gdGhlIHJlbGF0aW9uc2hpcCBpcyB2ZXJ5IHdlYWsgaW4gdGhlIGZpcnN0IHBsYWNlIHNvIHRoZXNlIGRpZmZlcmVudCBjb2VmZmljaWVudHMgYXJlIG5vdCBuZWNlc3NhcmlseSBzdGF0aXN0aWNhbGx5IHNpZ25pZmljYW50bHkgZGlmZmVyZW50LgoKQWxsIHRoZSBhY3R1YWwgdmFsdWVzIGFyZSBoZXJlOgoKCmBgYHtyfQpzdGF0c19kZgpgYGAKCgoKIyMjIENvbXBhcmUgcHNldWRvYnVsayB0byBidWxrIFRQTQoKCk5vdyB3ZSdsbCBkbyB0aGUgc2FtZSBmb3IgcHNldWRvYnVsayBhbmQgYnVsayBUUE0KCmBgYHtyIGZpZy5oZWlnaHQgPSAxMiwgZmlnLndpZHRoID0gMTIsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9Cm1ha2VfYmlubmVkX3NjYXR0ZXJwbG90cygKICBwcm9qZWN0X3dpZGVfZGZfbGlzdCRTQ1BDUDAwMDAwMSwgCiAgcHJvamVjdF9pZCA9ICJTQ1BDUDAwMDAwMSIsCiAgeXZhciA9IGJ1bGtfdHBtLAogIG5iaW5zID0gNDAsCiAgZmFjZXRfcm93cyA9IDYKKQpgYGAKCgoKYGBge3IgZmlnLmhlaWdodCA9IDEwLCBmaWcud2lkdGggPSAxMiwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KbWFrZV9iaW5uZWRfc2NhdHRlcnBsb3RzKAogIHByb2plY3Rfd2lkZV9kZl9saXN0JFNDUENQMDAwMDAyLCAKICBwcm9qZWN0X2lkID0gIlNDUENQMDAwMDAyIiwKICB5dmFyID0gYnVsa190cG0sCiAgbmJpbnMgPSAzMCwKICBmYWNldF9yb3dzID0gNgopCmBgYAoKCmBgYHtyIGZpZy5oZWlnaHQgPSAxMiwgZmlnLndpZHRoID0gMTIsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9Cm1ha2VfYmlubmVkX3NjYXR0ZXJwbG90cygKICBwcm9qZWN0X3dpZGVfZGZfbGlzdCRTQ1BDUDAwMDAwNiwgCiAgcHJvamVjdF9pZCA9ICJTQ1BDUDAwMDAwNiIsCiAgeXZhciA9IGJ1bGtfdHBtLAogIG5iaW5zID0gNTAsCiAgZmFjZXRfcm93cyA9IDkKKQpgYGAKCgpgYGB7ciBmaWcuaGVpZ2h0ID0gMywgZmlnLndpZHRoID0gMTIsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9Cm1ha2VfYmlubmVkX3NjYXR0ZXJwbG90cygKICBwcm9qZWN0X3dpZGVfZGZfbGlzdCRTQ1BDUDAwMDAwOSwgCiAgcHJvamVjdF9pZCA9ICJTQ1BDUDAwMDAwOSIsCiAgeXZhciA9IGJ1bGtfdHBtLAogIG5iaW5zID0gMTUsCiAgZmFjZXRfcm93cyA9IDEKKQpgYGAKCgpgYGB7ciBmaWcuaGVpZ2h0ID0gOCwgZmlnLndpZHRoID0gMTIsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9Cm1ha2VfYmlubmVkX3NjYXR0ZXJwbG90cygKICBwcm9qZWN0X3dpZGVfZGZfbGlzdCRTQ1BDUDAwMDAxNywgCiAgcHJvamVjdF9pZCA9ICJTQ1BDUDAwMDAxNyIsCiAgeXZhciA9IGJ1bGtfdHBtLAogIG5iaW5zID0gNDAsCiAgZmFjZXRfcm93cyA9IDcKKQpgYGAKCldlIHNlZSBoaWdoIGNvbmNlbnRyYXRpb25zIG9mIHBvaW50cyBhcm91bmQgYCgwLDBgKSBhcyB3ZWxsIGFzIHRvd2FyZHMgdGhlIG1pZGRsZSB2YWx1ZXMgYWNyb3NzIHBsb3RzLgpOZXh0LCB3ZSdsbCBsb29rIGF0IHJlZ3Jlc3Npb24gc3RhdHMgZm9yIHRoZXNlIHBsb3RzIGRpcmVjdGx5LgoKCiMjIyMgU3RhdGlzdGljcwoKTGV0J3Mgbm93IGdldCBzb21lIHN0YXRzIGZvciB0aGUgY29tcGFyaXNvbiBiZXR3ZWVuIGJ1bGsgVFBNIGFuZCBwc2V1ZG9idWxrLgpXZSdsbCBmaXQgYSBsaW5lYXIgbW9kZWwgZm9yIGVhY2ggc2FtcGxlLCBhbmQgZGlzcGxheSBzb21lIHF1YW50aXRpZXMgYmVsb3cgYm90aCBhcyBib3hwbG90cyBhbmQgdGhlIGZ1bGwgdGFibGUuCgpgYGB7ciBmaWcuaGVpZ2h0ID0gMTAsIGZpZy53aWR0aCA9IDEyfQoKbW9kZWxfc2FtcGxlc190cG0gPC0gZnVuY3Rpb24oaWQsIGRmKSB7CiAgc2FtcGxlX2RmIDwtIGRmIHw+CiAgICBkcGx5cjo6ZmlsdGVyKHNhbXBsZV9pZCA9PSBpZCkgCiAgCiAgZGZfZGVzZXEgPC0gc2FtcGxlX2RmIHw+CiAgICBkcGx5cjo6ZmlsdGVyKGlzLmZpbml0ZShwc2V1ZG9idWxrX2Rlc2VxKSwgaXMuZmluaXRlKGJ1bGtfdHBtKSkKICBmaXRfZGVzZXEgPC0gc3VtbWFyeShsbShidWxrX3RwbSB+IHBzZXVkb2J1bGtfZGVzZXEsIGRhdGEgPSBkZl9kZXNlcSkpCgogIGRmX2xvZ19jb3VudHMgPC0gc2FtcGxlX2RmIHw+CiAgICAgIGRwbHlyOjpmaWx0ZXIoaXMuZmluaXRlKHBzZXVkb2J1bGtfbG9nX2NvdW50cyksIGlzLmZpbml0ZShidWxrX3RwbSkpCiAgZml0X2xvZ19jb3VudHMgPC0gc3VtbWFyeShsbShidWxrX3RwbSB+IHBzZXVkb2J1bGtfbG9nX2NvdW50cywgZGF0YSA9IGRmX2xvZ19jb3VudHMpKQogICAgICAKICAjIFRhYnVsYXRlIGFuZCByZXR1cm4gc29tZSBmaXQgc3RhdHMKICBkYXRhLmZyYW1lKAogICAgZXhwcmVzc2lvbl90eXBlID0gYygiZGVzZXEiLCAibG9nX2NvdW50cyIpLAogICAgcnNxdWFyZWQgPSBjKGZpdF9kZXNlcSRyLnNxdWFyZWQsIGZpdF9sb2dfY291bnRzJHIuc3F1YXJlZCksIAogICAgY29lZmYgPSBjKGZpdF9kZXNlcSRjb2VmZmljaWVudHNbMl0sIGZpdF9sb2dfY291bnRzJGNvZWZmaWNpZW50c1syXSksIAogICAgcmVzaWR1YWxfc2QgPSBjKGZpdF9kZXNlcSRzaWdtYSwgIGZpdF9sb2dfY291bnRzJHNpZ21hKQogICkKfQoKc3RhdHNfZGYgPC0gcHJvamVjdF93aWRlX2RmX2xpc3QgfD4KICBwdXJycjo6bWFwKAogICAgXChkZikgewogICAgICAKICAgICAgIyBXZSBuZWVkIHRvIG1hcCBvdmVyIHNhbXBsZSBpZHMgbm93CiAgICAgIHNhbXBsZXMgPC0gdW5pcXVlKGRmJHNhbXBsZV9pZCkKICAgICAgbmFtZXMoc2FtcGxlcykgPC0gc2FtcGxlcwogICAgICAKICAgICAgZml0X3RhYmxlIDwtIHNhbXBsZXMgfD4KICAgICAgICBwdXJycjo6bWFwKG1vZGVsX3NhbXBsZXNfdHBtLCBkZikgfD4KICAgICAgICBwdXJycjo6bGlzdF9yYmluZChuYW1lc190byA9ICJzYW1wbGVfaWQiKQogICAgICAKICAgICAgcmV0dXJuKGZpdF90YWJsZSkKCiAgICB9CiAgKSB8PgogICMgbm93LCBjb21iaW5lIGFsbCBwcm9qZWN0cyBpbnRvIGEgc2luZ2xlIHRhYmxlCiAgcHVycnI6Omxpc3RfcmJpbmQobmFtZXNfdG8gPSAicHJvamVjdF9pZCIpCgpwYXRjaHdvcms6OndyYXBfcGxvdHMoCiAgcGxvdF9zdGF0cyhzdGF0c19kZiwgcnNxdWFyZWQsICJyc3F1YXJlZCIpLAogIHBsb3Rfc3RhdHMoc3RhdHNfZGYsIGNvZWZmLCAiY29lZmYiKSwgCiAgcGxvdF9zdGF0cyhzdGF0c19kZiwgcmVzaWR1YWxfc2QsICJyZXNpZHVhbF9zZCIpLCAKICBucm93ID0gMwopIApgYGAKCiogUHNldWRvYnVsayBxdWFudGl0aWVzIGFyZSBleGNlcHRpb25hbGx5IHNpbWlsYXIgaGVyZSwgd2hpY2ggaXNuJ3QgbmVjZXNzYXJpbHkgc3VycHJpc2luZyBnaXZlbiB0aGUgc2ltaWxhcml0eSBvZiB0aGUgcHNldWRvYnVsayBtZWFzdXJlcwoqIFJlbGF0aW9uc2hpcHMgYXJlIHN0cm9uZ2VzdCBmb3JgU0NQQ1AwMDAwMDFgIGFuZCBgU0NQQ1AwMDAwMDJgLCB0aGVuIGBTQ1BDUDAwMDAwNmAsIHRoZW4gYFNDUENQMDAwMDA5YCwgYW5kIGZpbmFsbHkgYFNDUENQMDAwMDE3YCB3aG9zZSByZWxhdGlvbnNoaXAgaXMgd2VhayBpZiBhdCBhbGwgcHJlc2VudC4KKiBTYW1wbGVzIHdpdGhpbiBhIGdpdmVuIHByb2plY3QgaGF2ZSBicm9hZGx5IHNpbWlsYXIgY29lZmZpY2llbnRzLCB3aXRoIGEgZmV3IG91dGxpZXJzLCBzdWdnZXN0aW5nIGxlc3Mgb2YgYW4gaW50ZXJhY3Rpb24gYW1vbmcgc2FtcGxlcy9leHByZXNzaW9uIHRoYW4gb25lIG1pZ2h0IGhhdmUgdGhvdWdodC4KYFNDUENQMDAwMDE3YCBkb2VzIGhhdmUgbW9yZSB2YXJpYXRpb24gaGVyZSwgYnV0IGFsc28gdGhlIHJlbGF0aW9uc2hpcCBpcyB2ZXJ5IHdlYWsgaW4gdGhlIGZpcnN0IHBsYWNlIHNvIHRoZXNlIGRpZmZlcmVudCBjb2VmZmljaWVudHMgYXJlIG5vdCBuZWNlc3NhcmlseSBzdGF0aXN0aWNhbGx5IHNpZ25pZmljYW50bHkgZGlmZmVyZW50LgoKQWxsIHRoZSBhY3R1YWwgdmFsdWVzIGFyZSBoZXJlOgoKCmBgYHtyfQpzdGF0c19kZgpgYGAKCgojIyBEaXNhZ3JlZWluZyBleHByZXNzaW9uCgpDdXJyZW50bHkgdGhpcyBzZWN0aW9uIGRvZXMgbm90IGluY2x1ZGUgYnVsayBjb3VudHMsIG9ubHkgYnVsayBUUE0uCgpOZXh0LCB3ZSdsbCB0YWtlIGEgcXVpY2sgbG9vayBhdCBjYXNlcyB3aGVyZSBvbmUgbW9kYWxpdHkgaGFzIHplcm8gZXhwcmVzc2lvbiBhbmQgdGhlIG90aGVyIGRvZXNuJ3QuCkluIHRoZXNlIGNhc2VzLCBpZiBleHByZXNzaW9uIGlzIGdlbmVyYWxseSBoaWdoLCB3ZSBoYXZlIGV2aWRlbmNlIG9mIGRpc2FncmVlbWVudC9kaXNjcmVwYW5jeSBiZXR3ZWVuIGJ1bGsgYW5kIHNpbmdsZS1jZWxsIHRoYXQgbWF5IGJlIGludGVyZXN0aW5nIHRvIGludmVzdGlnYXRlLgpJbiB0aGlzIG5vdGVib29rLCB3ZSdsbCBqdXN0IGEgc2Vuc2Ugb2YgaG93IG11Y2ggInRoZXJlIGlzIHRoZXJlLCIgYW5kIHdlJ2xsIGxlYXZlIHRoZSBpbi1kZXB0aCBsb29rIGludG8gYW55IHN1Y2ggZ2VuZXMgZm9yIGEgc3Vic2VxdWVudCBub3RlYm9vay4KCkluIHRoaXMgc2VjdGlvbiwgd2UnbGwgYWxzbyB1c2UgYSB0aHJlc2hvbGQgb2YgYDFlLTEyYCBmb3IgemVybyBoZXJlLgoKCiMjIyBCdWxrIFRQTSB3aGVuIHNpbmdsZS1jZWxsIGlzIHplcm8KCgpgYGB7ciBmaWcuaGVpZ2h0PTEwLCBmaWcud2lkdGg9MTIsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnByb2plY3Rfd2lkZV9kZl9saXN0IHw+CiAgcHVycnI6Om1hcCgKICAgIFwoZGYpIHsKICAgICAgCiAgICAgIGxvd19kZXNlcSA8LSBkZiB8PgogICAgICAgIGRwbHlyOjpmaWx0ZXIocHNldWRvYnVsa19kZXNlcSA8PSAxZS0xMiwgCiAgICAgICAgICAgICAgICAgICAgICBidWxrX3RwbSA+IDFlLTEyKQogICAgICBsb3dfbG9nY291bnRzIDwtIGRmIHw+CiAgICAgICAgZHBseXI6OmZpbHRlcihwc2V1ZG9idWxrX2xvZ19jb3VudHMgPD0gMWUtMTIsIAogICAgICAgICAgICAgICAgICAgICAgYnVsa190cG0gPiAxZS0xMikgIAoKICAgICAgCiAgICAgIHAxIDwtIGdncGxvdChsb3dfZGVzZXEpICsgCiAgICAgICAgYWVzKHggPSBzYW1wbGVfaWQsIHkgPSBidWxrX3RwbSkgKyAKICAgICAgICBnZ2ZvcmNlOjpnZW9tX3NpbmEoc2l6ZSA9IDAuNSkgKwogICAgICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpKSArCiAgICAgICAgZ2d0aXRsZSgiVFBNIGZvciB6ZXJvICYgbmVnYXRpdmUgcHNldWRvYnVsa19kZXNlcSIpCiAgCiAgICAgIHAyIDwtIGdncGxvdChsb3dfbG9nY291bnRzKSArIAogICAgICAgIGFlcyh4ID0gc2FtcGxlX2lkLCB5ID0gYnVsa190cG0pICsgCiAgICAgICAgZ2dmb3JjZTo6Z2VvbV9zaW5hKHNpemUgPSAwLjUpICsKICAgICAgICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSkgKwogICAgICAgIGdndGl0bGUoIlRQTSBmb3IgemVybyBwc2V1ZG9idWxrX2xvZ19jb3VudHMiKQogICAgICAKICAgICAgcGF0Y2h3b3JrOjp3cmFwX3Bsb3RzKHAxLCBwMiwgbnJvdyA9IDEpCiAgICB9CiAgKSB8PgogIHBhdGNod29yazo6d3JhcF9wbG90cyhuY29sID0gMSkKYGBgCgoKCiMjIyBTaW5nbGUtY2VsbCB3aGVuIGJ1bGsgVFBNIGlzIHplcm8KCgoKYGBge3IgZmlnLmhlaWdodD0xMiwgZmlnLndpZHRoPTEyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpwcm9qZWN0X3dpZGVfZGZfbGlzdCB8PgogIHB1cnJyOjppbWFwKAogICAgXChkZiwgcHJvamVjdF9pZCkgewogICAgICAKICAgICAgbG93X2J1bGsgPC0gZGYgfD4KICAgICAgICAjIEV2ZW4gdGhvdWdoIHdlIGRvbid0IGhhdmUgMToxIGNvcnJlc3BvbmRlbmNlIGJldHdlZW4gcHNldWRvYnVsa3MgaGVyZSwKICAgICAgICAjICB3ZSdsbCBqdXN0IGNvbnNpZGVyIG9ubHkgcG9pbnRzIHdoZXJlIG5laXRoZXIgaXMgMCB0byBnZXQgYSBzZW5zZS4KICAgICAgICBkcGx5cjo6ZmlsdGVyKGJ1bGtfdHBtIDw9IDFlLTEyLCAKICAgICAgICAgICAgICAgICAgICAgIHBzZXVkb2J1bGtfZGVzZXE+IDFlLTEyLCAKICAgICAgICAgICAgICAgICAgICAgIHBzZXVkb2J1bGtfbG9nX2NvdW50cz4gMWUtMTIpIHw+CiAgICAgICAgdGlkeXI6OnBpdm90X2xvbmdlcigKICAgICAgICAgIGNvbnRhaW5zKCJwc2V1ZG9idWxrIiksIAogICAgICAgICAgbmFtZXNfdG8gPSAiZXhwcmVzc2lvbl90eXBlIiwgCiAgICAgICAgICB2YWx1ZXNfdG8gPSAiZXhwcmVzc2lvbiIKICAgICAgICApCiAgICAgIAogICAgICBnZ3Bsb3QobG93X2J1bGspICsgCiAgICAgICAgYWVzKHggPSBzYW1wbGVfaWQsIHkgPSBleHByZXNzaW9uLCBjb2xvciA9IGV4cHJlc3Npb25fdHlwZSkgKyAKICAgICAgICBnZ2ZvcmNlOjpnZW9tX3NpbmEoc2l6ZSA9IDAuNSkgKwogICAgICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpKSArCiAgICAgICAgZ2d0aXRsZShwcm9qZWN0X2lkKQoKICAgIH0KICApIHw+CiAgcGF0Y2h3b3JrOjp3cmFwX3Bsb3RzKG5jb2wgPSAxLCBndWlkZXMgPSAiY29sbGVjdCIpCmBgYAoKCgpGcm9tIGJvdGggY29tcGFyaXNvbnMsIHRoZXJlIGFyZSBhIGZhaXIgbnVtYmVyIG9mIGdlbmVzIHdpdGggaGlnaCBleHByZXNzaW9uIGluIG9uZSBtb2RhbGl0eSBhbmQgZXNzZW50aWFsbHkgemVybyBpbiB0aGUgb3RoZXIuIApBIG1vcmUgY2FyZWZ1bCBpbnZlc3RpZ2F0aW9uIGhlcmUgbG9vayBpbnRvIHdoYXQgZXhhY3RseSB0aGVzZSBnZW5lcyBhcmUsIGFuZCB3aGV0aGVyIHRoZXkgaGF2ZSBzb21lIGJpb2xvZ2ljYWwgcmVsYXRpb25zaGlwIHRoYXQgbWlnaHQgc3VnZ2VzdCBtb2RhbGl0aWVzIGFyZSBwaWNraW5nIHVwIGRpZmZlcmVudCBpbmZvcm1hdGlvbi4KCgojIyMgRG8gcHNldWRvYnVsa3MgaGF2ZSBzdHJvbmcgZGlzYWdyZWVtZW50cz8KClRoaXMgd2lsbCBzaG93IGdlbmVzIHN0cm9uZ2x5IGFmZmVjdGVkIGJ5IGRpZmZlcmVudCBwcmVwYXJhdGlvbi9ub3JtYWxpemF0aW9uIGFwcHJvYWNoZXMuCgoKYGBge3IgZmlnLmhlaWdodD0xMiwgZmlnLndpZHRoPTEyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpwcm9qZWN0X3dpZGVfZGZfbGlzdCB8PgogIHB1cnJyOjppbWFwKAogICAgXChkZiwgcHJvamVjdF9pZCkgewogICAgICAKICAgICAgZGlmZmVyZW50X2xvd19wc2V1ZG8gPC0gZGYgfD4KICAgICAgICBkcGx5cjo6ZmlsdGVyKChwc2V1ZG9idWxrX2Rlc2VxPiAxZS0xMiAmIHBzZXVkb2J1bGtfbG9nX2NvdW50cyA8PSAxZS0xMikgfAogICAgICAgICAgICAgICAgICAgICAgIChwc2V1ZG9idWxrX2Rlc2VxIDw9MWUtMTIgJiBwc2V1ZG9idWxrX2xvZ19jb3VudHMgPiAxZS0xMikgKSB8PgogICAgICAgIHRpZHlyOjpwaXZvdF9sb25nZXIoCiAgICAgICAgICBjb250YWlucygicHNldWRvYnVsayIpLCAKICAgICAgICAgIG5hbWVzX3RvID0gImV4cHJlc3Npb25fdHlwZSIsIAogICAgICAgICAgdmFsdWVzX3RvID0gImV4cHJlc3Npb24iCiAgICAgICAgKQogICAgICAKICAgICAgZ2dwbG90KGRpZmZlcmVudF9sb3dfcHNldWRvKSArIAogICAgICAgIGFlcyh4ID0gc2FtcGxlX2lkLCB5ID0gZXhwcmVzc2lvbiwgY29sb3IgPSBleHByZXNzaW9uX3R5cGUpICsgCiAgICAgICAgZ2dmb3JjZTo6Z2VvbV9zaW5hKHNpemUgPSAwLjUpICsKICAgICAgICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSkgKwogICAgICAgIGdndGl0bGUocHJvamVjdF9pZCkKCgogICAgfQogICkgfD4KICBwYXRjaHdvcms6OndyYXBfcGxvdHMobmNvbCA9IDEsIGd1aWRlcyA9ICJjb2xsZWN0IikKYGBgCgpNb3N0IHZhbHVlcyBzZWVtIHRvIGJlIGBbLTIuNSwgMi41XWAgZm9yIGJvdGggcHNldWRvYnVsa3MsIGJ1dCBzb21lIGFyZSBnZXR0aW5nIGFzIGhpZ2ggYXMgNi05LiAKCgojIyBTZXNzaW9uIGluZm8KCmBgYHtyfQpzZXNzaW9uSW5mbygpCmBgYA==